package com.wdb.common.configurer.authority;

import com.wdb.common.configurer.subject.SubjectContext;
import com.wdb.common.constant.Constant;
import com.wdb.common.util.JacksonUtils;
import com.wdb.common.util.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class AuthorityInterceptor extends HandlerInterceptorAdapter {
    private AuthorityExecutor authorityExecutor;

    private AuthorityProperties authorityProperties;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        return !handled(handler) || authorized(handler) || allow(request, response);
    }

    private boolean handled(Object handler) {
        return handler instanceof HandlerMethod;
    }

    private boolean authorized(Object handler) {
        HandlerMethod method = (HandlerMethod) handler;

        Authorized authorized = method.getMethodAnnotation(Authorized.class);

        if (Objects.nonNull(authorized)) {
            return authorized.value();
        }

        authorized = method.getBeanType().getAnnotation(Authorized.class);

        return Objects.nonNull(authorized) && authorized.value();
    }

    private boolean allow(HttpServletRequest request, HttpServletResponse response) {
        String uri = request.getRequestURI();

        String jwt = request.getHeader(authorityProperties.getHeader());

        if (StringUtils.isEmpty(jwt)) {
            unauthorized(response, uri + " access without json web token");

            return false;
        }

        try {
            Jws<Claims> jws = authorityExecutor.parse(jwt);

            Claims claims = jws.getBody();

            Date iat = claims.getIssuedAt();

            Date exp = claims.getExpiration();

            SubjectContext.setSubject(claims.get(Constant.Context.SUBJECT, Long.class));

            if (Objects.equals((exp.getTime() - iat.getTime()) / 1000, authorityProperties.getRefresh())) {
                unauthorized(response, uri + " access with refresh json web token");

                return false;
            }

            return true;
        } catch (Exception e) {
            unauthorized(response, uri + " access parse failed json web token");
            return false;
        }
    }

    private void unauthorized(HttpServletResponse response, String error) {
        response.setStatus(401);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");

        try (PrintWriter writer = response.getWriter()) {


            Map<String, Object> map = new HashMap<>();

            map.put("error", error);

            String json = JacksonUtils.convert(map);

            if (logger.isDebugEnabled() && StringUtils.isNotEmpty(error)) {
                logger.info("{}", error);
            }

            writer.append(json);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        SubjectContext.remove();
    }

    public void setAuthorityExecutor(AuthorityExecutor authorityExecutor) {
        this.authorityExecutor = authorityExecutor;
    }

    public void setAuthorityProperties(AuthorityProperties authorityProperties) {
        this.authorityProperties = authorityProperties;
    }
}
